import java.io.BufferedOutputStream;
import java.io.BufferedReader;
//import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
//import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Random;

/**
 * Assignment 3 - OpenSSL
 * CS-5480
 * @author Long Nguyen
 */

public class SSLNode {

	String _name;
	String _mypath;				// folder path where Alice or Bob's keys are stored
	String _othername;
	String _otherpath;
	int _myport;				// Alice's defautl port= 6666
	int _otherport;				// Alice's defautl otherport= 7777
	
	String _AlicesMsg = "messageFromAlice.txt";		// path to Alice's message
	
	String _privateKeyPath;		// Alice or Bob's private key
	String _publicKeyPath;
	
	String _BobsSignedPublicKey = "BobsSignedPublicKey.bin";	// Bob's signed public key to be sent to Alice
	String _AlicesPublicKey;		// Alice's public key that's known to Bob
	
	String commonPrivateKeyPath;	// private key that Bob uses to sign his public key
	String commonPublicKeyPath;		// public key that Alice uses to verify Bob's public key
		
	String _BobsTarfile = "signedPublicKey.tar.gz";
	String _BobsPublicKey = "publicKeyBob.pem";	
	
	String _AlicesSignedHash = "AlicesSignedHash.bin";
	String _AlicesTarDigest = "AlicesTarDigest.tar.gz";
	String _AlicesTarEncrypted = "AlicesTarDigestEnc.bin";
	String _AlicesPasswdSigned = "AlicesPasswdSigned.bin";
	String _AlicesFinalMsg = "AlicesFinalMsg.tar.gz";
	
	String _passwordFile = "password.txt";
	
	ServerSocket _serverSocket;
	Socket _socket;
	OutputStream socketOutStream;
	InputStream clientInputStream;
	
	String _host;
	
	State _state;
	
	enum State { ReceiveBobsPublicKey, BobSendingPublicKey, VerifyBobsKey,
					AliceSendingMsg, ReceiveAlicesMsg, Idle };
	
	
	public SSLNode(int name) {		
		
		if (name == 1) {
			_name = "Alice";
			_othername = "Bob";			
			_myport = 6666;
			_otherport = 7777;			
			
//			try {
//				msgPath = (new File(".").getCanonicalPath()) + File.separator + "messageFromAlice.txt";
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
		}
		else {
			_name = "Bob";
			_othername = "Alice";
			_myport = 7777;
			_otherport = 6666;
		}
						
		try {
			_host = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			e.printStackTrace();
			System.exit(1);
		}
		System.out.println("I am " + _name);
		System.out.println("My TCP port: " + _myport);
		System.out.println("My IP address: " + _host);
	}
	
	public SSLNode(int name, int myport) {		
		this(name);
		
		_myport = myport;		
	}
	
//	public SSLNode(int name, int myport, String host, int otherport) {
//		this(name, myport);
//		
//		_host = host;
//		_otherport = otherport;
//	}
	
	public SSLNode(int name, int myport, int otherport) {
		this(name, myport);
				
		_otherport = otherport;
	}
	
	public void start() {
		_mypath = createFolder(_name);
		String currentDir = "";
		try {
			currentDir = new File(".").getCanonicalPath();
		} catch (IOException e) {
			e.printStackTrace();
		}
		_otherpath = currentDir + File.separator + _othername + File.separator;
		
		if (!generateKeys()) {
			System.exit(1);
		}
		
		createConnections();		
		
		loop();
	}


	private void loop() {
		boolean temp = true;
		while (temp) 
		{
			switch (_state) {
			case ReceiveBobsPublicKey:
				receiveBobsKey();
				break;
			case BobSendingPublicKey:
				sendBobsKey();
				break;
			case VerifyBobsKey:
				verifyBobsKey();				
				break;
			case AliceSendingMsg:
				sendAlicesMsg();
				_state = State.Idle;
				break;
			case ReceiveAlicesMsg:
				receiveAlicesMsg();
				_state = State.Idle;
				break;
			case Idle:
//				while (true) {
//					try {
//						Thread.sleep(2000);
//					} catch (InterruptedException e) { 
//						e.printStackTrace();
//					}
//				}
				temp = false;
				break;
			}			
		}
	}

	/**
	 * 
	 * @return
	 */
	private boolean sendAlicesMsg() {
		
		String[] cmds;				
		
		// Create signed digest of Alice's message with Alice's private key
		//
		cmds = new String[]{"openssl", "sha1", "-sign", _privateKeyPath,
							"-out", _AlicesSignedHash, _AlicesMsg};								
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds.toString());
			return false;
		}
		
		System.out.println("Alice created a signed digest of her message!");
		
		// Tarball +gzip Alice's message and signed hash
		//		
		cmds = new String[]{"tar", "-zcf", _AlicesTarDigest, 
							_AlicesMsg, _AlicesSignedHash};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		System.out.println("Alice tarred/gzipped her signed digest and her message!");
		
		// Remove Alice's original message so that Bob would not overwrite this
		//
		removeFileCmd(_AlicesMsg);
		
		
		// Generate password used for symmetric key
		//
		String passwd = createPassword(_mypath + _passwordFile);
		
		
		// Encrypt Alice's tarball file created earlier using 3DES with specified password
		//
		cmds = new String[]{"openssl", "enc", "-des3", "-in", _AlicesTarDigest,
							"-out", _AlicesTarEncrypted, "-pass", "pass:" + passwd };
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		System.out.println("Alice encrypted her file with 3DES!");
		
		// Create Alice's signed password file using Bob's public key
		//
		cmds = new String[]{"openssl", "rsautl", "-encrypt", "-pubin", "-inkey", _mypath + _BobsPublicKey,
							"-in", _mypath + _passwordFile, "-out", _AlicesPasswdSigned};								
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds.toString());
			return false;
		}
		
		System.out.println("Alice created a signed password file using Bob's public key!");
		
		// Combine/Tarball Alice's signed password file and 3DES-encrypted file		
		//
		cmds = new String[]{"tar", "-zcf", _AlicesFinalMsg, 
							_AlicesPasswdSigned, _AlicesTarEncrypted};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		System.out.println("Alice tarred/gzipped her final file!");
		
		// Move Alice's final msg to /Alice
		//
		copyFileCmd(_AlicesFinalMsg, _mypath + _AlicesFinalMsg);
		
		// Remove accompanied files 
		//
		removeFileCmd(_AlicesFinalMsg);
		removeFileCmd(_AlicesPasswdSigned);
		removeFileCmd(_AlicesTarEncrypted);
		removeFileCmd(_AlicesSignedHash);
		removeFileCmd(_AlicesTarDigest);
		
		
		try {
			_socket = _serverSocket.accept();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		System.out.println("Alice is sending her final file ....!");
		
		// Send Alice's final message to Bob
		//
		sendFile(_mypath + _AlicesFinalMsg);
		
		System.out.println("Alice has sent her final file to Bob!");
		
		return true;
	}

	/**
	 * Generates a random number for use as password,
	 * and stores this password in a file.
	 */
	private String createPassword(String filename) {

		Random gen = new Random();
		int passwd = gen.nextInt(100000);
		
		PrintWriter pwriter;
		
		System.out.println("Generating password for symmetric key ....");
		
		try {
			pwriter = new PrintWriter(new FileWriter(filename));
			pwriter.print(passwd);
			pwriter.close();
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
		System.out.println("password= " + passwd + " has been created.");
		return Integer.toString(passwd);
	}

	/**
	 * 
	 */
	private boolean receiveAlicesMsg() {
		
		// Bob connects to Alice
		while(true) {
			// keep making connection to Alice until successful
			//
			try {				
				Thread.sleep(1000);		
				_socket = new Socket( _host, _otherport);				
				break;
			
			} catch (IOException e) {
				System.err.println(e.toString());
				//e.printStackTrace();				
				//System.exit(1);
			} catch (InterruptedException e) { 
				e.printStackTrace();
				System.exit(1);
			}				
		}
		System.out.println("Receiving Alice's .tar file ...");
		
		receiveFile(_mypath + _AlicesFinalMsg);
		
		System.out.println("Bob received Alice's .tar file !!");
		
		copyFileCmd(_mypath+_AlicesFinalMsg, _AlicesFinalMsg);
		
		// Un-tar Alice's final tarred message
		// to obtain Alice's signed password file and encrypted .tar
		//
		String[] cmds;
		cmds = new String[]{"tar", "-zxf", _AlicesFinalMsg}; 
				
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Could not extract final message .tar");
			return false;
		}
		
		System.out.println("Bob untarred Alice's .tar file !");
		
		if (new File(_AlicesPasswdSigned).exists() && new File(_AlicesTarEncrypted).exists()) {
			
			System.out.println("Bob got Alice's RSA-signed password file and 3DES-encrypted file !");
			
			String passwd = decryptPasswd(_AlicesPasswdSigned);
			
			System.out.println("RSA-signed password= " + passwd);
			
			if (passwd.length()>0) {
				
				// De-crypt Alice's tarball file created earlier using 3DES with specified password
				//
				cmds = new String[]{"openssl", "enc", "-des3", "-d", "-in", _AlicesTarEncrypted,
									"-out", _AlicesTarDigest, "-pass", "pass:" + passwd };
				if ( executeCmd(cmds) != 0 ) {
					System.out.println("Error de-crypting Alice's tarred message: " + cmds);
					return false;
				}
				
				System.out.println("Bob de-crypted Alice's 3DES-encrypted file !");
				
				if (new File(_AlicesTarDigest).exists()) {
					
					// Check if Alice's msg is still there
					//
					if (new File(_AlicesMsg).exists()) {
						System.out.println("Alice's msg has not been removed !!");
					}
					else {
						System.out.println("Alice's msg has been deleted !!");
					}
					
					// Un-tar to obtain original message and msg's signed hash
					// 				
					cmds = new String[]{"tar", "-zxf", _AlicesTarDigest}; 					
					if ( executeCmd(cmds) != 0 ) {
						System.out.println("Could not extract message and signed digest .tar");
						return false;
					}
					
					System.out.println("Bob got Alice's signed message digest and her plain message!");
					
					if (new File(_AlicesMsg).exists() && new File(_AlicesSignedHash).exists()) {
						// Verify that Alice's original msg matches its signed hash
						// with Alice's public key
						//
						cmds = new String[]{"openssl", "sha1", "-verify", _AlicesPublicKey,
											"-signature", _AlicesSignedHash, _AlicesMsg}; 
						
						if ( executeCmd(cmds) != 0 ) {
							System.out.println("Alice's message did not match its signed hash !!");
							return false;
						}
						
						System.out.println("Bob verified Alice's plain message against its signed digest!");
						
						copyFileCmd(_AlicesMsg, _mypath + _AlicesMsg);						
						
						System.out.println("Bob has successfully received Alice's message !!!");
						System.out.println("Alice's message is now in Bob's folder !!!");
					}
					else {
						return false;
					}					
				}
				else {
					return false;
				}				
			}
			else {
				// password file does not contain a password
				return false;
			}
		}
		else {
			return false;
		}
		
		// Clean up
		//
		removeFileCmd(_AlicesSignedHash);
		removeFileCmd(_AlicesTarDigest);
		removeFileCmd(_AlicesTarEncrypted);
		removeFileCmd(_AlicesPasswdSigned);
		removeFileCmd(_passwordFile);
		removeFileCmd(_AlicesFinalMsg);
				
		return true;
	}

	/**
	 * 
	 */
	private String decryptPasswd(String passwordEnc) {

		String temp="";
		String[] cmds;
		
		// Extract password.txt
		//
		cmds = new String[]{"openssl", "rsautl", "-decrypt", "-inkey", _privateKeyPath,
							"-in", _AlicesPasswdSigned, "-out", _passwordFile};								
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Could not verify Alice's signed password file");
			System.exit(1);
		}
		
		if (new File(_passwordFile).exists()) {
			try{				
				FileInputStream fstream = new FileInputStream(_passwordFile);
				DataInputStream dataStream = new DataInputStream(fstream);			    
				BufferedReader br = new BufferedReader(new InputStreamReader(dataStream));
				String line;

				//Read only first line
				if ((line = br.readLine()) != null)   {
					temp = line;
				}
				br.close();
				dataStream.close();
				fstream.close();
			} catch (Exception e){
				System.err.println("Error: " + e.getMessage());
			}
		}
		return temp;
	}

	/**
	 * 
	 * @return
	 */
	private boolean verifyBobsKey() {
		
		System.out.println("Verifying Bob's key ...");

		// Un-tar Bob's signed key
		//
		String[] cmds;
		cmds = new String[]{"tar", "-zxf", _BobsTarfile}; 
				
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		// Remove Bob's tar file ...
		
		// Move Bob's public key to Alice's folder
		//
		//_BobsPublicKey = _mypath + _BobsPublicKey;
		//cmds = new String[]{"mv", new File(_BobsPublicKey).getName(),_BobsPublicKey };
		cmds = new String[]{"mv", _BobsPublicKey, _mypath + _BobsPublicKey };
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		_BobsSignedPublicKey = _mypath + _BobsSignedPublicKey;
		cmds = new String[]{"mv", new File(_BobsSignedPublicKey).getName(), _BobsSignedPublicKey };
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		// verify Bob's public key
		//
		cmds = new String[]{"openssl", "sha1", "-verify", commonPublicKeyPath, 
							"-signature", _BobsSignedPublicKey, _mypath + _BobsPublicKey};
		
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Bob's public key did not match its signed hash.");
			System.out.println("or Error executing command: " + cmds);
			return false;
		}
		
		System.out.println("Bob's public key was verified.");
		
		_state = State.AliceSendingMsg;
		
		return true;
	}

	/**
	 * 
	 * @return
	 */
	private boolean sendBobsKey() {

		System.out.println("Sending Bob's key ...");
		String[] cmds;
		
		// sign digest of Bob's public key using common private key
		//
		cmds = new String[]{"openssl", "sha1", "-sign", commonPrivateKeyPath, "-out", _BobsSignedPublicKey,
							_publicKeyPath};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		// Tarball + gzip Bob's files
		combineBobsFiles();
						
		// Sends tar file to Alice
		//
		sendFile(_BobsTarfile);
		
		// Remove Bob's tar file
		//
		cmds = new String[]{"rm", _BobsTarfile};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
			    
		System.out.println("Bob's key was sent.");
		
		_state = State.ReceiveAlicesMsg;
		
		return true;		
	}

	/**
	 * tarball + gzip Bob's public key and the just-created signed digest of Bob's public key
	 */
	private boolean combineBobsFiles() {
		
		String[] cmds;
		
		// change dir to Bob/
//		cmds = new String[]{"bash", "-c", "cd", _name + File.separator};
		
		// Copy from Bob's folder to up-folder
		//
		cmds = new String[]{"cp", _publicKeyPath, new File(_publicKeyPath).getName()};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		if (!copyFileCmd(_publicKeyPath, new File(_publicKeyPath).getName())) {
			return false;
		}

		if (!copyFileCmd(_BobsSignedPublicKey, new File(_BobsSignedPublicKey).getName())) {
			return false;
		}
		
		// tarball + gzip Bob's public key and the just-created signed digest of Bob's public key
		//
		//String tarFile = _mypath + File.separator + "signedPublicKey.tar.gz";
		_BobsTarfile = "signedPublicKey.tar.gz";
		
		cmds = new String[]{"tar", "-zcf", _BobsTarfile, 
							new File(_publicKeyPath).getName(), new File(_BobsSignedPublicKey).getName()};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
				
		// Remove copied files
		// 
		if (!removeFileCmd(new File(_publicKeyPath).getName())) {
			return false;
		}
		
//		cmds = new String[]{"rm", new File(_BobsSignedPublicKey).getName()};
//		if ( executeCmd(cmds) != 0 ) {
//			System.out.println("Error executing command: " + cmds);
//			return false;
//		}
		if (!removeFileCmd(new File(_BobsSignedPublicKey).getName())) {
			return false;
		}
				
		return true;
	}
	
	boolean copyFileCmd(String src, String des) {
		String[] cmds;
		cmds = new String[]{"cp", src, des};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		return true;
	}
	
	boolean removeFileCmd(String filename) {
		String[] cmds;
		cmds = new String[]{"rm", filename};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		return true;
	}

	/**
	 * Sends a file to output stream.
	 */
	private boolean sendFile(String filename) {
		
		File file = new File(filename);												
		FileInputStream fis;
		try {
			fis = new FileInputStream(file);
			// output stream to client
			socketOutStream = _socket.getOutputStream();	
			// Buffer file in byte array
			byte [] bytearray  = new byte [(int)file.length()];												
			
			int chunkSize;
		    while ( (chunkSize = fis.read(bytearray)) != -1 ) {
		    	// Send file to client
			    System.out.println("Transfering file...");
		    	socketOutStream.write(bytearray, 0, chunkSize);				    	
		    }
		    				    
		    socketOutStream.flush();		    
		    System.out.println("File sent.");
		    fis.close();
		    socketOutStream.close();
		    
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;		
	}

	
	/**
	 * Alice receives Bob's signed public key.
	 */
	private void receiveBobsKey() {
		
		_BobsTarfile = _mypath + _BobsTarfile; 
		System.out.println("Alice is receiving Bob's tar key ...");
		receiveFile(_BobsTarfile);
		_state = State.VerifyBobsKey;
	}

	
	/**
	 * Downloads/Receives data from input stream and saves/stores to filename.
	 */
	private boolean receiveFile(String filename) {
		int bufferSize = 2048;
		
		// byte array contains downloaded file
	    byte [] buffer  = new byte [ bufferSize ];
	    int readSize; 
        int totalSize = 0;
        
        try {
        	// get the socket's input stream 
        	clientInputStream = _socket.getInputStream();
        	FileOutputStream fileOutStream;
        	BufferedOutputStream bufferedOutStr;

        	// create a buffered output stream to write file to local disk
        	fileOutStream = new FileOutputStream( new File( filename ) );                       
        	bufferedOutStr = new BufferedOutputStream(fileOutStream);

        	while ((readSize = clientInputStream.read(buffer)) != -1) {
        		System.out.println("Downloading....");
        		totalSize += readSize;
        		bufferedOutStr.write(buffer, 0, readSize);
        	}            
        	bufferedOutStr.flush();			// flush data to disk, VERY IMPORTANT

        	System.out.println("File " + filename + " has been received.");

        	// Close streams after each download
        	fileOutStream.close();
        	bufferedOutStr.close();
        	clientInputStream.close();
        }
        catch (IOException ex) {
        	System.out.println(ex);
        	return false;
        }
        
		return true;
	}

	private void createConnections() {
		
		// Alice is the server, Bob is the client
		// Connected via TCP sockets on the same machine, hence using same IP
		
		if (_name.equals("Alice")) {
			try {
				System.out.println("Waiting for connection from Bob ...");
				
				_serverSocket = new ServerSocket(_myport, 5, null );								
				
				_socket = _serverSocket.accept();
				
				System.out.println("Connection established.");
				System.out.println("Waiting for Bob's public key...");
				_state = State.ReceiveBobsPublicKey;
				
			} catch (IOException e) {
				e.printStackTrace();
				System.exit(1);
			}
		}
		else {
			// Bob connects to Alice
			while(true) {
				// keep making connection to Alice until successful
				//
				try {				
					Thread.sleep(1000);

					//_socket = new Socket( "localhost", _otherport);
					_socket = new Socket( _host, _otherport);
					//_socket = new Socket(InetAddress.getLocalHost(), _otherport);
					_state = State.BobSendingPublicKey;
					break;

				} catch (UnknownHostException e) {
					e.printStackTrace();
					System.err.println(e.toString());
					//System.exit(1);
				} catch (IOException e) {
					System.err.println(e.toString());
					//e.printStackTrace();				
					//System.exit(1);
				} catch (InterruptedException e) { 
					e.printStackTrace();
					System.exit(1);
				}				
			}
			
			System.out.println("Connection established.");
			System.out.println("Sending Bob's public key to Alice...");
		}						
	}

	/**
	 * 
	 */
	private boolean generateKeys() {
		
		String[] cmds = null;
		_privateKeyPath = _mypath + "privateKey" + _name + ".pem";
		_publicKeyPath = _mypath + "publicKey" + _name + ".pem";
		
		// private key
		//
		cmds = new String[]{"openssl", "genrsa", "-out", _privateKeyPath, "1024"};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		// public key
		//
		cmds = new String[]{"openssl", "rsa", "-in", _privateKeyPath, "-pubout", "-out", _publicKeyPath};
		if ( executeCmd(cmds) != 0 ) {
			System.out.println("Error executing command: " + cmds);
			return false;
		}
		
		// common key
		//		
		
		if (_name.equals("Alice")) {
			// Alice holds the common public key of Bob's common private key
			//
			commonPublicKeyPath = _mypath + "commonPublicKey.pem";
			commonPrivateKeyPath = _otherpath + "commonPrivateKey.pem";
			File commonPrivateKeyFile = new File(commonPrivateKeyPath);
			
			while (!commonPrivateKeyFile.exists()) {
				// wait until Bob's common private key is available
				//
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.exit(1);
				}
			}
			cmds = new String[]{"openssl", "rsa", "-in", commonPrivateKeyPath, "-pubout", "-out", commonPublicKeyPath};
			
			if ( executeCmd(cmds) != 0 ) {
				System.out.println("Error executing command: " + cmds);
				return false;
			}			
		}
		else if (_name.equals("Bob")) {
			// Bob holds the common private key to sign his own public key
			//
			commonPrivateKeyPath = _mypath + "commonPrivateKey.pem";
			_BobsSignedPublicKey = _mypath + _BobsSignedPublicKey;
			
			// Bob knows Alice's public key
			//
			try {
				_AlicesPublicKey = (new File(".").getCanonicalPath()) + File.separator + "Alice" + 
									File.separator + "publicKeyAlice.pem";
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			cmds = new String[]{"openssl", "genrsa", "-out", commonPrivateKeyPath, "1024"};
			
			if ( executeCmd(cmds) != 0 ) {
				System.out.println("Error executing command: " + cmds);
				return false;
			}
		}
				
		return true;
	}
	
	/**
	 * 
	 */
	int executeCmd(String[] cmds) {
		
		Runtime rt = Runtime.getRuntime();
        Process proc;
        int exitVal = 0;
        
		try {
			proc = rt.exec(cmds);
			
			// Display console messages 
			//
//			InputStream stderr = proc.getErrorStream();
//            InputStreamReader isr = new InputStreamReader(stderr);
//            BufferedReader br = new BufferedReader(isr);
//            String line = null;
//            System.out.println("<CONSOLE>");
//            while ( (line = br.readLine()) != null)
//                System.out.println(line);
//            System.out.println("</CONSOLE>");
            
			exitVal = proc.waitFor();
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {			
			e.printStackTrace();
		}
        //System.out.println("Process exit code: " + exitVal);
        return exitVal;
	}
	
	/**
	 * Creates "folderName" folder if not exist.
	 * Returns path to this newly created folder.
	 * Also deletes existing folder, or will overwrite files in this folder
	 * if folder could not be deleted. 
	 */
	static String createFolder(String folderName) {
		
		String currentDir="";
		try {
			currentDir = new File(".").getCanonicalPath();			
			folderName = currentDir + File.separator + folderName;
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
			
		File dir = new File(folderName);
		
		if (dir.exists()) {
			// folder exists
			//
			if (!deleteDir(dir)) {
				// could not delete folder, will overwrite files in this folder
				//
				System.out.println("Could not delete folder \"" + folderName +"\"");
				System.out.println("Will overwrite files in \"" + folderName +"\"");
				return folderName + File.separator;
			}
		}
		
		// folder does not exist
		//
		if (!dir.mkdir()) {
			System.out.println("Could not create \"" + folderName +"\"");
			return currentDir;
		}
		// Success
		//
		return folderName + File.separator;		
	}
	
	/**
	 * Deletes all files and subdirectories under dir. 
	 * Returns true if all deletions were successful. 
	 * If a deletion fails, the method stops attempting to delete and returns false.
	 */
	static boolean deleteDir(File dir) { 
		if (dir.isDirectory()) { 
			String[] children = dir.list(); 
			for (int i=0; i<children.length; i++) { 
				boolean success = deleteDir(new File(dir, children[i]));
				if (!success) { 
					return false; 
				} 
			} 
		} 
		// The directory is now empty so delete it 
		return dir.delete(); 
	}
	
	/*
	 * Called before exit. Cleans up ServerSocket objects.
	 * (non-Javadoc)
	 * @see java.lang.Object#finalize()
	 */
	protected void finalize() {
		try {
//			if (socketOutStream != null) {
//				socketOutStream.close();
//			}
//			if (clientInputStream != null) {
//				clientInputStream.close();
//			}
			
			if (_socket != null) {
				_socket.close();
			}
			if (_serverSocket != null) {
				_serverSocket.close();
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}
